﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using GA.Server.Configuration;
using System.Configuration;
using GA.Server.Api;
using System.Windows.Threading;
using System.Reflection;
using log4net.Config;
using log4net.Repository.Hierarchy;
using log4net.Core;
using log4net;
using System.Collections.ObjectModel;

namespace GA.Server.Main
{
    public class Program
    {
        /// <summary>
        /// Gets the currently running program.
        /// </summary>
        public static Program CurrentProgram
        {
            get { return mCurrentProgram; }
        }

        /// <summary>
        /// Gets a collection of all the running servers.
        /// </summary>
        public ReadOnlyCollection<GAServer> RunningServers
        {
            get { return mRunningServers.AsReadOnly(); }
        }

        /// <summary>
        /// Gets a collection of all the available servers.
        /// </summary>
        public ReadOnlyCollection<ServerElement> AvailableServers
        {
            get { return mAvailableServers.AsReadOnly(); }
        }

        /// <summary>
        /// Starts a server element in the program.
        /// </summary>
        /// <param name="forceLoad">If true, force load the dll information from disk.</param>
        /// <param name="server">ServerElement to start.</param>
        /// <param name="properties">Properties collection to post.</param>
        /// <exception cref="ArgumentNullException">Thrown if server is null.</exception>
        public void StartServer(bool forceLoad, ServerElement server, ServerPropertiesElementCollection properties)
        {
            if (server == null) throw new ArgumentNullException("server");

            mLog.InfoFormat("Starting Server {0}...", server.Name);
            GAServer gaServer = server.GetServerInstance(false, properties);
            mRunningServers.Add(gaServer);
        }

        /// <summary>
        /// Gets the currently running server of the specified name.
        /// </summary>
        /// <param name="serverName">Name of the server.</param>
        /// <returns>First server that matches the specified name, null if nothing is found.</returns>
        /// <exception cref="ArgumentNullException">Thrown if serverName is null.</exception>
        /// <exception cref="ArgumentException">Thrown if serverName is empty.</exception>
        public GAServer GetRunningServer(string serverName)
        {
            if (serverName == null) throw new ArgumentNullException("serverName");
            if (serverName == string.Empty) throw new ArgumentException("serverName cannot be empty.", "serverName");

            if (RunningServers.Any((server) => { return server.Name.Equals(serverName, StringComparison.InvariantCultureIgnoreCase); }))
            {
                return RunningServers.First((server) => { return server.Name.Equals(serverName, StringComparison.InvariantCultureIgnoreCase); });
            }
            return null;
        }

        public T GetRunningServer<T>(string serverName) where T : GAServer
        {
            if (serverName == null) throw new ArgumentNullException("serverName");
            if (serverName == string.Empty) throw new ArgumentException("serverName cannot be empty.", "serverName");

            return (T)GetRunningServer(serverName);
        }

        /// <summary>
        /// Gets all of the currently running servers of the specified name.
        /// </summary>
        /// <param name="serverName">Name of the server.</param>
        /// <returns>A list of servers that match the specified name.</returns>
        /// <exception cref="ArgumentNullException">Thrown if serverName is null.</exception>
        /// <exception cref="ArgumentException">Thrown if serverName is empty.</exception>
        public IEnumerable<GAServer> GetRunningServers(string serverName)
        {
            if (serverName == null) throw new ArgumentNullException("serverName");
            if (serverName == string.Empty) throw new ArgumentException("serverName cannot be empty.", "serverName");

            if (RunningServers.Any((server) => { return server.Name.Equals(serverName, StringComparison.InvariantCultureIgnoreCase); }))
            {
                return new List<GAServer>(RunningServers.TakeWhile((server) => { return server.Name.Equals(serverName, StringComparison.InvariantCultureIgnoreCase); }));
            }
            return new List<GAServer>();
        }

        public IEnumerable<T> GetRunningServers<T>(string serverName) where T : GAServer
        {
            if (serverName == null) throw new ArgumentNullException("serverName");
            if (serverName == string.Empty) throw new ArgumentException("serverName cannot be empty.", "serverName");

            List<T> servers = new List<T>();
            foreach (GAServer server in GetRunningServers(serverName))
            {
                if (server is T)
                {
                    servers.Add((T)server);
                }
            }
            return servers;
        }

        /// <summary>
        /// Gets the currently running server of the specified process id.
        /// </summary>
        /// <param name="processId">Process identifier to look up.</param>
        /// <returns>Server that matches the specified name.</returns>
        /// <exception cref="ArgumentException">Thrown if processId is less than or equal to zero.</exception>
        public GAServer GetRunningServer(int processId)
        {
            if (processId <= 0) throw new ArgumentException("processId must be greater than zero.", "processId");

            if (RunningServers.Any((server) => { return server.ProcessId == processId; }))
            {
                return RunningServers.First((server) => { return server.ProcessId == processId; });
            }
            return null;
        }

        static void Main(string[] args)
        {
            mCurrentProgram = new Program();
            mCurrentProgram.Start();
        }

        private static Program mCurrentProgram;
        private List<GAServer> mRunningServers;
        private List<ServerElement> mAvailableServers;
        private ILog mLog;

        private Program()
        {
            mRunningServers = new List<GAServer>();
            mAvailableServers = new List<ServerElement>();
        }

        private void Start()
        {
            XmlConfigurator.Configure();

            mLog = (ILog)LogManager.GetLogger("GA.Server.Main");
            mLog.Info("Starting GAServer Main...");

            if (ConfigurationManager.GetSection("Servers") == null)
            {
                mLog.Fatal("No Servers were defined in the App.config. Closing.", new InvalidOperationException("No Servers were defined in the App.config. Closing."));
                return;
            }

            ServerConfigurationSection section = (ServerConfigurationSection)ConfigurationManager.GetSection("Servers");
            SortedList<string, ServerElement> orderedServers = new SortedList<string, ServerElement>();
            foreach (ServerElement element in section.Servers)
            {
                if (element.StartupOrder > -1)
                {
                    orderedServers.Add(string.Format("{0}_{1}", element.StartupOrder.ToString("000000"), element.Name), element);
                }
                else
                {
                    orderedServers.Add(element.Name, element);
                }
            }
            foreach (ServerElement element in orderedServers.Values)
            {
                mAvailableServers.Add(element);
                if (element.LoadOnStartup)
                {
                    StartServer(false, element, element.ServerProperties);
                }
            }

            List<IConsoleAction> consoleActions = new List<IConsoleAction>();
            foreach (Type type in Assembly.GetCallingAssembly().GetTypes())
            {
                if (type.IsAssignableFrom(typeof(IConsoleAction)) && !type.IsInterface)
                {
                    consoleActions.Add((IConsoleAction)type.GetConstructor(new Type[] { }).Invoke(new object[] { }));
                }
            }
            ConsoleActionResult lastResult = ConsoleActionResult.CompletedSuccessfully;
            while (!lastResult.IsShutdown())
            {
                Console.Out.Write("Console Ready: ");
                string readLine = Console.ReadLine();
                Console.Out.WriteLine();
                if (readLine.Length > 0)
                {
                    string command = readLine.Split(' ')[0];
                    if (consoleActions.Any((action) => { return action.Name.Equals(command, StringComparison.InvariantCultureIgnoreCase); }))
                    {
                        lastResult = consoleActions.First((action) => { return action.Name.Equals(command, StringComparison.InvariantCultureIgnoreCase); })
                            .RunAction(readLine.Substring(readLine.IndexOf(' ')));
                    }
                    else
                    {
                        Console.WriteLine("No such command.");
                    }
                }
                
            }
        }

    }
}
